
package com.scalar.chiptrack.utils;

import java.io.File;
import java.io.IOException;
import java.util.StringTokenizer;
import java.util.logging.ConsoleHandler;
import java.util.logging.FileHandler;
import java.util.logging.Level;
import java.util.logging.LogRecord;
import java.util.logging.SimpleFormatter;

import com.scalar.ScalarException;


/**
 * <p> Logger class writes log messages onto the console or into a file based on the properties set 
 * in scalar.properties file. Typical usage of this class looks more like 
 * 
 * <code>
 *      Logger logger = Logger.getLogger();     // in constructor or at the top of the class
 *              ...
 *              ...
 *      logger.debug ("simple debug message");
 *      logger.info ("chiptrack.sales.dao.SalesDAO", "info level message, also providing soource class name");
 *      logger.warning ("chiptrack.users.Admin", "user not found", new scalar.chiptrack.users.UserNotFoundException());
 *      logger.error ("chiptrack.adaptor.extractor.TSMCExtractor", "File not found", new scalar.chiptrack.adaptor.DataExtractorException());
 * </code>
 *
 * For Logger class to work property 5 properties have to be set to proper values in scalar.properties file
 *
 * <ul>
 *  <li><b>logger.target=console,file</b> <-- where to log the messages
 *  <li><b>logger.logDir=c:/workingdir/logs</b> <-- where to store the logs, new file is created every time the class is loaded
 *  <li><b>logger.level=debug</b> <-- either debug, info, warning or error
 *  <li><b>logger.maxFileSize=4096</b> <-- max number of bytes to be written in one log file
 *  <li><b>logger.defaultPrefix=Scalar.Chiptrack</b> <-- default prefix, used when classname is not specified in the log method
 * </ul>
 */
public final class Logger extends SimpleFormatter
{
    public static final String LOG_LEVEL_DEBUG   = "debug";
    public static final String LOG_LEVEL_INFO    = "info";
    public static final String LOG_LEVEL_WARNING = "warning";
    public static final String LOG_LEVEL_ERROR   = "error";

    public static final String LOG_HANDLER_TYPE_CONSOLE = "console";
    public static final String LOG_HANDLER_TYPE_FILE    = "file";

    private String mChipTracLogLevel = "debug"; //default

    private static final String LOG_FILE_NAME = "chiptrack%u.log";

    public static final int DEBUG   = 0;
    public static final int INFO    = 1;
    public static final int WARNING = 2;
    public static final int ERROR   = 3;

    private static Logger sLogger = null;
    private java.util.logging.Logger mJavaLogger = null;
    
    private Logger()
    {
        String logDir = null;
        try
        {
            logDir = validateDirectory ( ConfigManager.getLogDirectory() );
        }
        catch( ScalarException se )
        {
            System.out.println( "Exception while initializing Logger: " + se.getMessage() );
        }

        if( logDir == null )
        {
            return;
        }

        mChipTracLogLevel = ConfigManager.getLogLevel();
        String[] targets = parseTargets( ConfigManager.getLogTarget() );

        mJavaLogger = java.util.logging.Logger.getLogger( ConfigManager.getLogPrefix() );
        mJavaLogger.setLevel( Level.FINEST );
        mJavaLogger.setUseParentHandlers( false );

        if( targets != null )
        {
            for( int i=0; i<targets.length; i++ )
            {
                // Console Logging
                if( targets[i].equalsIgnoreCase( LOG_HANDLER_TYPE_CONSOLE ) == true )
                {
                    ConsoleHandler consoleHandler = new ConsoleHandler();
                    consoleHandler.setLevel( Level.FINEST );
                    mJavaLogger.addHandler( consoleHandler );
                }
                // File Logging
                if( targets[i].equalsIgnoreCase( LOG_HANDLER_TYPE_FILE ) == true )
                {
                    try
                    {
                        FileHandler fileHandler = new FileHandler( logDir + LOG_FILE_NAME, ConfigManager.getMaxLogFileSize(), 1, true );
                        fileHandler.setFormatter( new SimpleFormatter() );
                        fileHandler.setLevel( Level.FINEST );
                        mJavaLogger.addHandler( fileHandler );
                    }
                    catch (IOException ioe)
                    {
                        System.out.println( "Could not create file \"" + logDir + LOG_FILE_NAME + "\"" );
                        System.out.println( "Exception message = " + ioe.getMessage() );
                    }
                }
            }
        }
    }
    
    
    // Specific to "file" targets
    public Logger (String logDirectory, String logFileName, String logPrefix, String level, int maxFileSize, int fileCount) throws ScalarException {
        
        String logDir = validateDirectory (logDirectory);

        mJavaLogger = java.util.logging.Logger.getLogger (logPrefix);
        mJavaLogger.setLevel( Level.FINEST );
        mJavaLogger.setUseParentHandlers( false );
        
        try
        {
            FileHandler fileHandler = new FileHandler (logDir + logFileName, maxFileSize, fileCount, true);
            fileHandler.setFormatter (this);
            fileHandler.setLevel( Level.FINEST );
            mJavaLogger.addHandler( fileHandler );
        }
        catch( IOException ioe )
        {
            System.out.println( "Could not create file \"" + logDir + logFileName + "\"" );
            System.out.println( "Exception message = " + ioe.getMessage() );
            throw new ScalarException( "Could not create logger", ioe );
        }
    }
    
    
    
    public static Logger getLogger()
    {
        if (sLogger == null) {
            sLogger = new Logger();
        }
        
        return sLogger;
    }
    
    public void logDebug( String message )
    {
        if( mChipTracLogLevel.indexOf( LOG_LEVEL_DEBUG ) != -1 )
        {
            log( DEBUG, message );
        }
    }

    public static void debug( String message )
    {
        getLogger().logDebug( message );
    }

    public void logDebug( String message, Throwable throwable )
    {
        if( mChipTracLogLevel.indexOf( LOG_LEVEL_DEBUG ) != -1 )
        {
            message += "\n" + getStatckTraceAsString( throwable );
            log( DEBUG, message );
        }
    }

    public static void debug( String message, Throwable throwable )
    {
        getLogger().logDebug( message, throwable );
    }

    public void logInfo( String message )
    {
        if( mChipTracLogLevel.indexOf( LOG_LEVEL_INFO ) != -1 )
        {
            log( INFO, message );
        }
    }

    public static void info( String message )
    {
        getLogger().logInfo( message );
    }

    public void logInfo( String message, Throwable exception )
    {
        if( mChipTracLogLevel.indexOf( LOG_LEVEL_INFO ) != -1 )
        {
            message += "\n" + getStatckTraceAsString( exception );
            log( INFO, message );
        }
    }

    public static void info( String message, Throwable exception )
    {
        getLogger().logInfo( message, exception );
    }

    public void logWarning( String message )
    {
        if( mChipTracLogLevel.indexOf( LOG_LEVEL_WARNING ) != -1 )
        {
            log( WARNING, message );
        }
    }

    public static void warning( String message )
    {
        getLogger().logWarning( message );
    }

    public void logWarning( String message, Throwable exception )
    {
        if( mChipTracLogLevel.indexOf( LOG_LEVEL_WARNING ) != -1 )
        {
            message += "\n" + getStatckTraceAsString( exception );
            log( WARNING, message );
        }
    }

    public static void warning( String message, Throwable exception )
    {
        getLogger().logWarning( message, exception );
    }

    public void logError( String message )
    {
        if( mChipTracLogLevel.indexOf( LOG_LEVEL_ERROR ) != -1 )
        {
            log( ERROR, message );
        }
    }

    public static void error( String message )
    {
        getLogger().logError( message );
    }

    public void logError( String message, Throwable exception )
    {
        if( mChipTracLogLevel.indexOf( LOG_LEVEL_ERROR ) != -1 )
        {
            message += "\n" + getStatckTraceAsString( exception );
            log( ERROR, message );
        }
    }

    public static void error( String message, Throwable exception )
    {
        getLogger().logError( message, exception );
    }

    private void log( int level, String message )
    {
        Level logLevel = Level.FINEST;
        
        switch( level )
        {
            case DEBUG:
                logLevel = Level.FINEST;
                break;
                
            case INFO:
                logLevel = Level.INFO;
                break;
                
            case WARNING:
                logLevel = Level.WARNING;
                break;
                
            case ERROR:
                logLevel = Level.SEVERE;
                break;
                
            default:
                logLevel = Level.FINEST;
                break;                
        }
        
        mJavaLogger.log( logLevel, message );
    }
 
    
    private String[] parseTargets (String targetList) {
        if (targetList == null) {
            return null;
        }
        StringTokenizer st = new StringTokenizer (targetList.trim(), ",");
        String[] targets = new String[st.countTokens()];
        
        int i=0;
        while (st.hasMoreTokens() == true) {
            targets[i++] = st.nextToken();
        }
        return targets;
    }
 
    
    private String validateDirectory (String dir) throws ScalarException
    {
        
        if (dir == null)
        {
            throw new ScalarException ("Specified Log Directory is null");
        }

        File file = new File (dir);
        if (file.exists() == false)
        {
            if (file.mkdirs() == false)
            {
                throw new ScalarException ("Could not create specified log directory, " + dir);
            }
        }
        else if (file.isDirectory() == false)
        {
            throw new ScalarException ("Specified log directory is not a directory");
        }

        if (file.canRead() == false)
        {
            throw new ScalarException ("Cannot read log directory \"" + dir + "\"");
        }

        if (file.canWrite() == false)
        {
            throw new ScalarException ("Cannot write into log directory \"" + dir + "\"");
        }

        if (!dir.endsWith("/") || !dir.endsWith("\\"))
        {
            dir += "/";
        }

        return dir;
    }

    public String format( LogRecord record )
    {
        return record.getMessage();
    }

    private String getStatckTraceAsString( Throwable throwable )
    {
        StringBuffer errMessage = new StringBuffer();
        if( throwable != null )
        {
            errMessage.append( throwable.toString() ).append( "\n" );

            StackTraceElement[] stElement = throwable.getStackTrace();
            for( int i=0; i<stElement.length; i++ )
            {
                errMessage.append( stElement[i].toString() ).append( "\n" );
            }
        }

        return errMessage.toString();
    }

    // test
    public static void main (String[] args) throws ScalarException
    {
        Logger.debug( "debug message\n" );
        Logger.debug( "message\n", new Exception() );
        
        Logger.info( "message\n" );
        Logger.info( "message\n", new Exception() );
        
        Logger.warning( "message\n" );
        Logger.warning( "message\n", new Exception() );
        
        Logger.error ("message\n");
        Logger.error( "message\n", new Exception() );
    }
}